package org.acooke.uykfd.cmd

import java.io.File
import java.sql.Connection

import org.acooke.uykfd.Main
import org.acooke.uykfd.db.ArtistRow
import org.acooke.uykfd.db.Dump
import org.acooke.uykfd.db.Schema
import org.acooke.uykfd.db.Properties._
import org.acooke.uykfd.lastfm.TopTags
import org.acooke.uykfd.mp3.Importer
import org.acooke.uykfd.walk._

import org.apache.log4j.Level

import scala.Function.tupled
import scala.util.matching.Regex
import scala.util.parsing.combinator.RegexParsers 

class ExitException extends RuntimeException


object Dispatcher extends RegexParsers {

  def command = exit | show | set | backup | read | build | walk | logging | help

  def exit = ("exit" | "quit") ^^ ((_) => 
    (cnxn: Connection) => throw new ExitException)

  def help = ("help" | "?") ^^ ((_) => 
    (cnxn: Connection) => println("see http://code.google.com/p/uykfd/wiki/Commands\n"))

  def show = {
    def properties = "properties" ^^ ((_) =>
      (cnxn: Connection) => props.show(cnxn))
    def lastfm = {
      def artist = {
        def tags = longString ^^ ((name) =>
          (cnxn: Connection) => Schema.LFM_ARTIST_TAGS.orm.show(name, cnxn))
        def connections = longString ^^ ((name) =>
          (cnxn: Connection) => Schema.LFM_ARTIST_CONNECTIONS.orm.show(name, cnxn))
        def backlinks = longString ^^ ((name) =>
          (cnxn: Connection) => Schema.LFM_ARTIST_BACKLINKS.orm.show(
            name, props.BACK_BACK(cnxn), props.BACK_FWD(cnxn), cnxn))
        def distance = longString ~ longString ^^ ((pair) =>
          (cnxn: Connection) => Schema.LFM_ARTIST_DISTANCES.orm.show(pair._1, pair._2, cnxn))
        def distances = longString ^^ ((name) =>
          (cnxn: Connection) => Schema.LFM_ARTIST_DISTANCES.orm.show(name, cnxn))
        "artist " ~> ( (tags <~ "tags") 
                     | (connections <~ "connections")
                     | (backlinks <~ "backlinks")
                     | (distance <~ "distance")
                     | (distances <~ "distances")
                     )
      }
      def artists = {
        def random = "random" ^^ ((_) =>
          (cnxn: Connection) => println(Schema.LFM_ARTIST_TAGS.orm.randomTaggedArtist(cnxn).value))
        def backlinks = 
          "backlinks" ^^ ((_) =>
            (cnxn: Connection) => Schema.LFM_ARTIST_BACKLINKS.orm.show(cnxn))
        "artists " ~> (random | backlinks)
      }
      def tagged = {
        "tagged" ~> "artists" ^^ ((_) =>
          (cnxn: Connection) => Schema.LFM_ARTIST_TAGS.orm.showTagged(cnxn))
      }
      def untagged = {
        "untagged" ~> "artists" ^^ ((_) =>
          (cnxn: Connection) => Schema.LFM_ARTIST_TAGS.orm.showUntagged(cnxn))
      }
      "lastfm " ~> (artist | artists | tagged | untagged)
    }
    "show" ~> (properties | lastfm)
  }

  def set = "set " ~> word ~ longString ^^ ((pair) =>
    (cnxn: Connection) => props(pair._1, cnxn).pushValue(pair._2))

  def backup = {
    def export = "export " ~> longString ^^ ((path) => 
      (cnxn: Connection) => Dump.dumpAll(new File(path), cnxn))
    def inport = "import " ~> longString ^^ ((path) => 
      (cnxn: Connection) => Dump.restoreAll(new File(path), cnxn))
    export | inport
  }

  def read = {
    def mp3 = "mp3s " ~> longString ^^ ((path) =>
      (cnxn: Connection) => {
        val prefix = props.PATH_IN(cnxn)
        new Importer(cnxn).loadPath(prefix, new File(prefix, path))
    })
    def lastfm = {
      def artist = "artist " ~> longString ^^ ((name) => 
        (cnxn: Connection) => new TopTags(cnxn).get(name))
      def artists = "artists" ^^ ((_) =>
        (cnxn: Connection) => 
          new TopTags(cnxn).all(props.LASTFM_MAXPOLL(cnxn), props.LASTFM_MAXRATE(cnxn)))

      "lastfm " ~> (artist | artists)
    }
    "read " ~> (mp3 | lastfm)
  }

  def build = {
    def lastfm = {
      def artist = {
        def connections = "connections" ^^ ((_) =>
          (cnxn: Connection) => 
            Schema.LFM_ARTIST_CONNECTIONS.orm.build(cnxn))
        def backlinks = "backlinks" ^^ ((_) =>
          (cnxn: Connection) => 
            Schema.LFM_ARTIST_BACKLINKS.orm.build(
              props.BACK_BACK(cnxn), props.BACK_FWD(cnxn), cnxn))
        def backlink = {
          def distances = "distances" ^^ ((_) =>
          (cnxn: Connection) => 
            Schema.LFM_ARTIST_DISTANCES.orm.build(
              Schema.LFM_ARTIST_BACKLINKS.orm.toDirectedGraph(cnxn), cnxn))
          "backlink " ~> distances
        }
        "artist " ~> (connections | backlinks | backlink)
      }
      "lastfm " ~> artist
    }
    def auto = "auto"  ^^ ((_) => progressive _)
    "build " ~> (lastfm | auto)
  }

  def walk = {
    def lastfm = {
      def artist = {
        def connections = (longString ~ number <~ "connections") ^^ ((pair) =>
          (cnxn: Connection) => {
            possiblyRandomArtist(pair._1, cnxn) match {
              case Some(artist) =>
                new ArtistWalk(artist,
                               props.ARTIST_MEMORY(cnxn), props.TRACK_MEMORY(cnxn), 
                               props.ARTIST_DEXPONENT(cnxn), props.ARTIST_PENALTY(cnxn),
                               props.PATH_OUT(cnxn), cnxn).print(Integer.parseInt(pair._2))
              case None => println(pair._1 + " not found")
            }})
        def backlink = (longString ~ number <~ "backlinks") ^^ ((pair) =>
          (cnxn: Connection) => {
            possiblyRandomArtist(pair._1, cnxn) match {
              case Some(artist) =>
                new BacklinkWalk(artist,
                                 props.ARTIST_MEMORY(cnxn), props.TRACK_MEMORY(cnxn),  
                                 props.ARTIST_DEXPONENT(cnxn), props.ARTIST_PENALTY(cnxn),
                                 props.PATH_OUT(cnxn), cnxn).print(Integer.parseInt(pair._2))
              case None => println(pair._1 + " not found")
            }})
        "artist " ~> (connections | backlink)
      }
      "lastfm " ~> artist
    }
    "walk " ~> lastfm
  }

  def logging = {
    def debug = "debug" ^^ ((_) => (cnxn: Connection) => Main.setLogging(Level.DEBUG))
    def info = "info" ^^ ((_) => (cnxn: Connection) => Main.setLogging(Level.INFO))
    def warn = "warn" ^^ ((_) => (cnxn: Connection) => Main.setLogging(Level.WARN))
    def error = "error" ^^ ((_) => (cnxn: Connection) => Main.setLogging(Level.ERROR))
    def fatal = "fatal" ^^ ((_) => (cnxn: Connection) => Main.setLogging(Level.FATAL))
    def level = (debug | info | warn | error | fatal)
    "logging " ~> level
  }

  def possiblyRandomArtist(name: String, cnxn: Connection): Option[ArtistRow] =
    Schema.ARTISTS.orm.lookupValue(
      if (name == "") Schema.LFM_ARTIST_TAGS.orm.randomTaggedArtist(cnxn).value else name,
      cnxn)

  def longString = longWord ^^ dropQuotes | word
  def dropQuotes(s: String) = s.substring(1, s.length - 1)
  def longWord = regex(new Regex("""\"[^\"]*\""""))
  def word = regex(new Regex("""\S+"""))
  def number = regex(new Regex("""\d+"""))

  def apply(line: String, cnxn: Connection) = {
    try {
      parseAll(command, line) match {
        case Success(result, _) => result(cnxn)
        case x => println(x)
      }
    } catch {
      case x: ExitException => throw x
      case x: AssertionError => println(x)
      case x: Exception => x.printStackTrace(System.out)
    }
  }

  def progressive(cnxn: Connection) {
    Schema.LFM_ARTIST_CONNECTIONS.orm.build(cnxn)
    Schema.LFM_ARTIST_BACKLINKS.orm.build(1, 1, cnxn)
    Schema.LFM_ARTIST_DISTANCES.orm.build(Schema.LFM_ARTIST_BACKLINKS.orm.toDirectedGraph(cnxn), cnxn)
    Schema.LFM_ARTIST_BACKLINKS.orm.build(2, 2, cnxn)
    Schema.LFM_ARTIST_DISTANCES.orm.extend(Schema.LFM_ARTIST_BACKLINKS.orm.toDirectedGraph(cnxn), cnxn)
    Schema.LFM_ARTIST_BACKLINKS.orm.build(3, 3, cnxn)
    Schema.LFM_ARTIST_DISTANCES.orm.extend(Schema.LFM_ARTIST_BACKLINKS.orm.toDirectedGraph(cnxn), cnxn)
    Schema.LFM_ARTIST_BACKLINKS.orm.build(6, 6, cnxn)
  }

}
